local ffi = require'ffi'
local bit = require'bit'
require'glue'

assert(ffi.abi'32bit', 'only known to work on 32bit platforms')
assert(ffi.abi'win', 'platform not Windows')

--create the only namespace for the entire API
local _MT = {}
local _M = setmetatable({}, _MT)

local function assert_up(level,v,e,...)
	if v then return v,e,... end
	e = e ~= nil and e or 'assertion failed'
	if select('#',...) > 0 then e = e:format(...) end
	error(e, level)
end

local function assert2(...) return assert_up(3,...) end

--set strict mode for the whole namespace
local _G = _G
function _MT:__index(k)
	assert2(_G[k] ~= nil, 'Undefined winapi global %s', k)
	return _G[k]
end

local declared = {}
function _MT:__newindex(k,v)
	local w = debug.getinfo(2, 'S').what
	assert2(w == 'main' or w == 'C', 'Assignment to undeclared winapi global %s', k)
	declared[k] = true
	rawset(self, k, v)
end

--make the module table, ffi and bit APIs accessible from submodules
_M._M = _M
_M.ffi = ffi
_M.bit = bit
_M.assert2 = assert2
_M.assert_up = assert_up

--that's it: we can begin our first submodule now
setfenv(1, _M)

--debug tools

--search the name of a constant in the winapi namespace (linear search!)
function findname(prefix, value)
	for k,v in pairs(_M) do
		if k:starts(prefix) and v == value then return k end
	end
	return tonumber(value) ~= nil and '%x' % value or value
end

local _print = print
_M._print = _print
function __print(indent,...) --print that can be used in expressions and dives into tables
	if indent ~= '' then _print(indent,...) else _print(...) end
	for i=1,select('#',...) do
		local t = select(i,...)
		if type(t) == 'table' then
			for k,v in pairs(t) do __print(indent..'       ',k,v) end
		end
	end
	return ...
end

function print(...) return __print('',...) end

--wrapping of API functions and arguments

local function checkwith(valid)
	return function(ret)
		local valid, err = valid(ret)
		if not valid then
			local code = GetLastError()
			if code ~= 0 then
				err = FormatMessageFromSystemA(code)
			end
			error(err,2)
		end
		return ret
	end
end

checkz   = checkwith(function(ret) return ret == 0, 'zero expected, got non-zero' end)
checknz  = checkwith(function(ret) return ret ~= 0, 'non-zero expected, got zero' end)
checkh   = checkwith(function(ret) return ret ~= nil, 'non NULL value expected, got NULL' end)
checkpoz = checkwith(function(ret) return ret >= 0, 'positive number expected, got negative' end)

function own(o, finalizer)
	--print('own', o)
	return o ~= nil and ffi.gc(o, finalizer) or nil
end

function disown(o)
	--print('disown', o)
	return o ~= nil and ffi.gc(o, nil) or nil
end

function flags(s) --accept and compute a 'FLAG1 | FLAG2' string; nil turns 0
	if s == nil then return 0 end
	if type(s) == 'string' then
		local x = 0
		for flag in (s..'|'):gmatch'([^|]+)|' do
			x = bit.bor(x, _M[flag:trim()])
		end
		return x
	end
	return s
end

function wcs(s) --accept and convert a utf8 Lua string to a wcs cdata
	if type(s) == 'string' then
		return (MultiByteToWideChar(s))
	else
		return s
	end
end

W = wcs --fancy sugar to use on string constants

function countfrom0(n) --adjust value from counting from 1 to counting from 0
	return type(n) == 'number' and n > 0 and n-1 or n
end

function countfrom1(n) --adjust value from counting from 0 to counting from 1
	return type(n) == 'number' and n >= 0 and n+1 or n
end

function splitlong(n) --returns the low and the high word of a long
	return bit.band(n, 0xffff), bit.rshift(n, 16)
end

--ctype wrapping and construction

types = {}
setmetatable(types, types)

function types:__index(type_str)
	local ctype = ffi.typeof(type_str)
	self[type_str] = function(t,...)
		if type(t) == 'cdata' then return t end
		if type(t) == 'nil' then return ctype() end
		return ctype(t,...)
	end
	return self[type_str]
end

--struct wrapping and construction

local function setbit(t, k, mask, set) --hot code
	if set then
		t[k] = bit.bor(t[k] or 0, mask)
	else
		t[k] = bit.band(t[k] or 0, bit.bnot(mask))
	end
	--print(t,k,mask,set,t[k])
end

local function struct_set(self, cdata, field, value) --hot code
	local def = self.fields[field]
	if def then
		local name, mask, cast = unpack(def, 1, 3)
		if mask then
			setbit(cdata, self.mask, mask, value ~= nil)
		end
		if name then
			if not cast then
				cdata[name] = value
			elseif type(cast) == 'string' then
				cdata[name] = ffi.cast(cast, value)
			else --table with __call or function
				cdata[name] = cast(value, cdata)
			end
		else
			cast(value, cdata) --custom setter
		end
		return
	end
	--masked bitfield support
	def = self.bitfields and self.bitfields[field]
	if def then
		for bitname, enabled in pairs(value) do
			cdata[field..'_'..bitname] = enabled
		end
		return
	else
		local fieldname, bitname = field:match'^([^_]+)_(.*)'
		if fieldname then
			def = self.bitfields[fieldname]
			if def then
				local datafield, maskfield, prefix = unpack(def)
				local mask = _M[prefix..'_'..bitname]
				if mask then
					setbit(cdata, maskfield, mask, value ~= nil)
					setbit(cdata, datafield, mask, value)
					return
				end
			end
		end
	end
	error('struct "%s" has no field "%s"' % {self.ctype, field}, 5)
end

local function struct_get(self, cdata, field, value) --hot code
	local def = self.fields[field]
	if def then
		local name, mask = unpack(def, 1, 2)
		if not mask then
			return cdata[name]
		elseif bit.band(cdata[self.mask], mask) ~= 0 then
			return cdata[name]
		else
			return nil
		end
	end
	--masked bitfield support
	local fieldname, bitname = field:match'^([^_]+)_(.*)'
	if fieldname then
		def = self.bitfields[fieldname]
		if def then
			local datafield, maskfield, prefix = unpack(def)
			local mask = _M[prefix..'_'..bitname]
			if mask then
				if bit.band(cdata[maskfield], mask) ~= 0 then
					return bit.band(cdata[datafield], mask) ~= 0
				else
					return nil
				end
			end
		end
	end
	error('struct "%s" has no field "%s"' % {self.ctype, field}, 5)
end

local function struct_cons(self, t)
	if type(t) == 'cdata' then return t end
	local cdata = self.ctype_cons()
	if self.size then cdata[self.size] = ffi.sizeof(cdata) end
	if self.defaults then
		for field, value in pairs(self.defaults) do
			cdata[field] = value
		end
	end
	if t then
		for field, value in pairs(t) do
			cdata[field] = value
		end
	end
	return cdata
end

local valid_struct_keys =
	index{'ctype', 'size', 'mask', 'fields', 'defaults', 'bitfields'}

local struct_class = {}

local struct_meta = {
	__call = struct_cons,
	__index = struct_class,
}

function struct(defs)
	--typecheck the struct definition
	assert2(defs.ctype ~= nil, 'ctype missing')
	for k,v in pairs(defs) do	--check for typos in struct definition
		assert_up(2, valid_struct_keys[k], 'invalid struct key "%s"', k)
	end
	if defs.fields then --check for accidentaly hidden fields
		for k,v in pairs(defs.fields) do
			local vname, sname, mask, cast = unpack(v, 1, 4)
			assert2(vname ~= sname, 'virtual field "%s" not visible', v[1])
		end
	end

	defs.ctype_cons = ffi.typeof(defs.ctype)
	ffi.metatype(defs.ctype_cons, {	--setup ctype for virtual fields
		__index = function(t,k)
			return struct_get(defs,t,k)
		end,
		__newindex = function(t,k,v)
			struct_set(defs,t,k,v)
		end,
	})
	return setmetatable(defs, struct_meta)
end

function sfields(t) --sugar constructor for defining non-masked struct fields
	local dt = {}
	for i=1,#t,3 do
		assert2(type(t[i]) == 'string', 'invalid sfields spec')
		assert2(type(t[i+1]) == 'string', 'invalid sfields spec')
		dt[t[i]] = {
			t[i+1] ~= '' and t[i+1] or nil,
			nil,
			(type(t[i+2]) ~= 'function' or t[i+2] ~= pass) and t[i+2] or nil
		}
	end
	return dt
end

function mfields(t) --sugar constructor for defining masked struct fields
	local dt = {}
	for i=1,#t,4 do
		assert2(type(t[i]) == 'string', 'invalid mfields spec')
		assert2(type(t[i+1]) == 'string', 'invalid mfields spec')
		assert2(type(t[i+2]) == 'number', 'invalid mfields spec')
		dt[t[i]] = {
			t[i+1] ~= '' and t[i+1] or nil,
			t[i+2] ~= 0 and t[i+2] or nil,
			t[i+3] ~= pass and t[i+3] or nil
		}
	end
	return dt
end

--array wrapping and construction

local array_ctypes = {}
function array(ctype, items)
	if type(items) == 'table' then
		local actype = array_ctypes[ctype]
		if not actype then
			actype = ffi.typeof(ctype..'[?]')
			array_ctypes[ctype] = actype
		end
		local array = ffi.new(actype, #items)
		for i=1,#items do
			array[i-1] = items[i]
		end
		return array, #items
	end
	return items, ffi.sizeof(items) / ffi.sizeof(ctype)
end

--[[
function struct_class:len(array_cdata)
	return ffi.sizeof(array_cdata) / ffi.sizeof(self.ctype_cons)
end

local function array_cons(self, t)
	if type(t) == 'nil' or type(t) == 'table' or type(t) == 'number' then
		local len = type(t) == 'table' and #t or type(t) == 'number' and t or 0
		local cdata = self.actype_cons(len)
		if type(t) == 'table' then
			for i=1,#t do
				--
			end
		end
	else
		return t
	end
end

function array(def)
	def.actype_cons = ffi.typeof(def.ctype..'[?]')
	return function(t)
		return array_cons(def,t)
	end
end
]]

return _M

